Inside Macintosh: Sound

| Previous | Chapter contents | Chapter top | Section top | Next |

Managing Multiple Sound Channels

If you are writing an application that can play multiple channels of sound on Macintosh computers that support that feature, you can use the Sound Manager's asynchronous playing abilities, but you might encounter some special obstacles. The technique for playing sounds asynchronously described in "Playing Sounds Asynchronously" has a limitation if you are using multiple sound channels. Using that technique without modification, you would need to define each separate sound channel in a different global variable, and you would need to use several global flags in your callback procedure to signal which sound channels have finished processing sound commands.

Although it is easy to modify the code in "Playing Sounds Asynchronously" to use several flags, this solution might not be satisfactory for an application in which the number of sound channels open can vary. For example, suppose that you are writing entertainment software with dozens of sound effects that correspond to actions on the screen and you wish to use the Sound Manager asynchronously so that several sound effects can be played at once. It would be cumbersome to associate a separate global sound channel variable with each sound and create a flag variable for each of these sound channels. Also, you might wish to play the same sound simultaneously in two separate channels. It would be better to write code that manages a global list of sound channels and then provides a simple routine that allows you to add a channel to the list. This section shows how you might implement such a list of sound channels. Listing 1-34 defines a data structure that you could use to track multiple sound channels.

Listing 34 Defining a data structure to track many sound channels

CONST
    kMaxNumSndChans = 20;                       {max number of sound channels}
TYPE
    SCInfo =
    RECORD
        sndChan:            SndChannelPtr;      {NIL or pointer to channel}
        mustDispose:        Boolean;            {flag to dispose channel}
        itsData:            Handle;             {data to dispose with channel}
    END;
    SCList = ARRAY[1..kMaxNumSndChans] OF SCInfo;
VAR
    gSndChans:              SCList;

The SCInfo data structure defined in Listing 1-34 allows you to keep track of which channels in the collection are being used and which were being used but currently need disposal; it also allows you to associate data with a sound channel so that you can dispose of the data when you dispose of the sound channel. Note that the value of the kMaxNumSndChans constant might vary from application to application. Having defined the data structure, you must initialize it (so that the sndChan and itsData fields are NIL and the mustDispose field is FALSE ). You must also write a procedure that finds an available channel. You might declare such a procedure like this:

PROCEDURE DoTrackChan (chanToTrack: SndChannelPtr; associatedData: Handle);

Using such a procedure, you could simply create sound channels by using local variables and then add them to the tracking list so that your application disposes of them when they finish executing. The exact implementation of such a procedure would depend on the needs of your application. For example, if there are no channels available in the global list of sound channels, your application might report an error, stop sound on all active channels, or stop sound on the channel that has been playing the longest. If you want your application to be compatible with computers that do not support multichannel sound, this procedure could check whether multichannel sound is supported, and if not, would stop any sound playing on other channels. This is particularly useful if your application plays sound effects in response to actions on the screen; overlapping sound effects sound best, but if this is unattainable, the newest sound should have the highest priority.

One advantage of maintaining a list of sound channels is that you can use it in conjunction with both callback procedures and completion routines. Listing 1-35 defines a procedure that either your callback procedure or completion routine could call after setting the application's A5 world correctly.

Listing 35 Marking a channel for disposal

PROCEDURE MySetTrackChanDispose (mySndChannel: SndChannelPtr);
VAR
    index:          Integer;            {channel index}
    found:          Boolean;            {flag variable}
BEGIN
    index := 1;                         {start at first spot}
    found := FALSE;                     {initialize flag variable}
    WHILE (index <= kMaxNumSndChans) AND (NOT found) DO
        IF gSndChans[index].sndChan = mySndChannel THEN
            found := TRUE               {proper channel found}
        ELSE
            index := index + 1;         {move to next spot}
    IF found THEN
        gSndChans[index].mustDispose := TRUE;
END;

The final thing you need to do is to define a procedure that your application calls once each time through its main event loop. This procedure must dispose of sound channels that are marked for disposal. Listing 1-36 defines such a routine.

Listing 36 Disposing of channels that have been marked for disposal

PROCEDURE MyCleanUpTrackedChans;
CONST
    kQuietNow = TRUE;                                           {need to quiet channel?}
VAR
    index:          Integer;
    myErr:          OSErr;
BEGIN
    FOR index := 1 TO kMaxNumSndChans DO                        {go through all channels}
    WITH gSndChans[index] DO
        IF mustDispose THEN                                     {check global flag}
        BEGIN                                                   {channel needs disposal}
            IF gSndChans[index].itsData <> NIL THEN
            BEGIN                                               {release other data}
                HUnlock(gSndChans[index].itsData);
                HPurge(gSndChans[index].itsData);
            END;
                                                                {free channel-related memory}
            myErr := MyDisposeSndChannel(sndChan, kQuietNow);
            sndChan := NIL;                                     {set pointer to NIL}
            mustDispose := FALSE;                               {reset global flag}
            IF myErr <> noErr THEN
                DoError(myErr);
        END;
END;

The MyCleanUpTrackedChans procedure defined in Listing 1-36 works just like the MyCheckSndChan procedure defined in Listing 1-30 , but instead of checking a single global flag, it checks the flag associated with each allocated sound channel. Now that you have defined such a procedure, you can easily write a routine to stop sound in all active channels (for example, if your application receives a suspend event). Simply set the mustDispose flag on all sound channels that are allocated (that is for all channels that are not NIL ) and then call MyCleanUpTrackedChans . Note, however, that when the MyCleanUpTrackedChans procedure disposes of a sound channel processing a play from disk, the completion routine will be called and will thus set the mustDispose flag to TRUE . Thus, the mustDispose flag must be reset to FALSE after the sound channel has been disposed. Otherwise, the MyCleanUpTrackedChans procedure would try to dispose of the same sound channel again when the application called it from its main event loop.


© 1998 Apple Computer, Inc.

| Previous | Chapter contents | Chapter top | Section top | Next |